package pub.pjoc.util.http;

import com.google.common.base.Strings;
import pub.pjoc.util.http.annotation.HttpParameter;
import pub.pjoc.util.http.annotation.ParameterIgnore;
import pub.pjoc.util.reflect.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

/**
 * @author xiongyingqi
 * @since 16-12-17 下午5:01
 */
public class HttpParameterHandler<T> {
  private static final Logger logger = LoggerFactory.getLogger(HttpParameterHandler.class);

  public Class<T> type;
  private Map<Field, String> fieldParameterNameMap = new HashMap<Field, String>();

  public HttpParameterHandler(Class<T> type) {
    this.type = type;
    initFieldParameterNameMap();
  }

  private void initFieldParameterNameMap() {
    synchronized (this) {
      fieldParameterNameMap.clear();
      Map<Field, String> map = parseClass(type);
      fieldParameterNameMap.putAll(map);
      logger.info("Initialized field parameters: {} by class: {}", fieldParameterNameMap, type);
    }
  }

  private boolean inited() {
    if (fieldParameterNameMap == null) {
      synchronized (this) {
        if (fieldParameterNameMap == null) {
          return false;
        }
      }
    }
    return true;
  }

  private void check() {
    if (!inited()) {
      initFieldParameterNameMap();
    }
  }

  public Map<String, String> getParameterMap(T instance) {
    if (instance == null) {
      return null;
    }
    check();
    return parseParameters(instance);
  }

  @SuppressWarnings("unchecked")
  public Map<String, String> getParameterMapWithObject(Object instance) {
    if (instance == null) {
      return null;
    }
    if (!instance.getClass().equals(type)) {
      return null;
    }
    T i = (T) instance;
    return getParameterMap(i);
  }

  private Map<String, String> parseParameters(T instance) {
    Map<String, String> map = new HashMap<String, String>();
    Field[] declaredFields = type.getDeclaredFields();
    for (Field declaredField : declaredFields) {
      if (Modifier.isStatic(declaredField.getModifiers()) || Modifier.isNative(declaredField.getModifiers())) {
        continue;
      }
      String name;
      if ((name = fieldParameterNameMap.get(declaredField)) == null) {
        if (logger.isDebugEnabled()) {
          logger.debug("Could'nt found parameter name on field: {}", declaredField);
        }
        continue;
      }
      Object fieldValue = null;
      try {
        fieldValue = ReflectionUtils.getFieldValue(declaredField, instance);
      } catch (IllegalAccessException e) {
        logger.error("", e);
      }
      if (fieldValue == null) {
        continue;
      }
      String value = fieldValue.toString();
      map.put(name, value);
    }
    return map;
  }


  private static Map<Field, String> parseClass(Class<?> clazz) {
    Map<Field, String> map = new HashMap<Field, String>();
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
      if (Modifier.isStatic(field.getModifiers()) || Modifier.isNative(field.getModifiers())) {
        continue;
      }
      String parameterName = getFieldParameterName(field);
      if (Strings.isNullOrEmpty(parameterName)) {
        continue;
      }
      map.put(field, parameterName);
    }
    return map;
  }

  public static String getFieldParameterName(Field field) {
    if (field.getAnnotation(ParameterIgnore.class) != null) {
      return null;
    }
    String methodName = null;

    if (!field.isAnnotationPresent(HttpParameter.class)) {
      methodName = parseMethod(field);
    }

    if (Strings.isNullOrEmpty(methodName)) {
      methodName = parseField(field);
    }
    return methodName;
  }

  public static String parseField(Field field) {
    HttpParameter annotation = AnnotationUtils.findAnnotation(field, HttpParameter.class);
    String value;
    if (annotation == null || Strings.isNullOrEmpty((value = annotation.value()))) {
      return field.getName();
    }
    return value;
  }

  public static String parseMethod(Field field) {
    try {
      Method getMethod = ReflectionUtils.findGetMethod(field);
      if (!getMethod.isAnnotationPresent(HttpParameter.class)) {
        return null;
      }
      HttpParameter annotation = AnnotationUtils.findAnnotation(getMethod, HttpParameter.class);
      if (annotation == null) {
        return null;
      }
      return annotation.value();
    } catch (NoSuchMethodException ignored) {
      return null;
    }
  }

}
